package com.benshou.bcss.common.lock;

import com.benshou.bcss.common.j17common.annotation.NotNull;
import lombok.NonNull;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.function.Supplier;

/**
 * 包装器：用于确保 {@link Condition} 正确执行 {@code await()}
 * <p>
 *     在原生调用 {@link Condition} 的 {@code await()} 方法时，有可能会因为虚拟机自身的原因意外提前返回。
 *     此时，资源有可能仍未可用。因此，使用此包装起用以确保 {@code await()} 到指定的时间后再返回。
 * </p>
 *
 * @author <a href="mailto:liuzhenbang@benshou.com">Liu Zhenbang</a>
 */
public record EnsureAwaitWrapper<T extends Condition>(
        @NonNull T condition) {

    /**
     * 调用 {@link Condition} 的 {@code await()}。（若资源未就绪）此方法可以确保 {@code await()} 到指定的时间后再返回。
     *
     * @param timeout 超时时间
     * @param unit 超时时间的单位
     * @param awaiting 具体的 {@code await()} 方法
     * @param test 测试资源是否已就绪的判断方法
     * @return 资源是否已就绪
     * @throws InterruptedException 若被外部中断，抛出此异常
     */
    public boolean await(
            long timeout,
            @NonNull TimeUnit unit,
            @NonNull Awaiting<? super T> awaiting,
            @NonNull Supplier<@NotNull Boolean> test) throws InterruptedException {
        var start = System.nanoTime();
        var timeoutNanos = unit.toNanos(timeout);

        boolean satisfied;
        // 注意：nanos 可能溢出（超过 273 年），必须使用减法运算
        var remainNanos = timeoutNanos - (System.nanoTime() - start);
        do {
            awaiting.await(this.condition, remainNanos, TimeUnit.NANOSECONDS);
        } while (!(satisfied = test.get()) && (remainNanos = timeoutNanos - (System.nanoTime() - start)) > 0);

        return satisfied;
    }

    /**
     *调用 {@link Condition} 的 {@code await()}。（若资源未就绪）此方法可以确保 {@code await()} 到指定的时间后再返回。
     *
     * @param timeout 超时时间
     * @param unit 超时时间的单位
     * @param test 测试资源是否已就绪的判断方法
     * @return 资源是否已就绪
     * @throws InterruptedException 若被外部中断，抛出此异常
     * @see EnsureAwaitWrapper#await(long, TimeUnit, Awaiting, Supplier)
     * @see Condition#await(long, TimeUnit)
     */
    public boolean await(
            long timeout,
            @NonNull TimeUnit unit,
            @NonNull Supplier<@NotNull Boolean> test) throws InterruptedException {
        @SuppressWarnings("ResultOfMethodCallIgnored")
        var awaiting = (Awaiting<T>) Condition::await;
        return await(timeout, unit, awaiting, test);
    }
}
